//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/StateManager.h"
#include "Graphics/GLshell/CachedUniform.h"
#include "Graphics/GLshell/Shader.h"

namespace Engine
{
namespace Graphics
{
	
	struct gl_uniform
	{
		enum type
		{
			_UNKNOWN			= 0,

			// Bool //
			BOOL				= gles::GL_BOOL,
			BOOL_VEC2			= gles::GL_BOOL_VEC2,
			BOOL_VEC3			= gles::GL_BOOL_VEC3,
			BOOL_VEC4			= gles::GL_BOOL_VEC4,
		
			// Float //
			FLOAT				= gles::GL_FLOAT,
			FLOAT_VEC2			= gles::GL_FLOAT_VEC2,
			FLOAT_VEC3			= gles::GL_FLOAT_VEC3,
			FLOAT_VEC4			= gles::GL_FLOAT_VEC4,
			FLOAT_MAT2			= gles::GL_FLOAT_MAT2,
			FLOAT_MAT3			= gles::GL_FLOAT_MAT3,
			FLOAT_MAT4			= gles::GL_FLOAT_MAT4,

			// Int //
			INT					= gles::GL_INT,
			INT_VEC2			= gles::GL_INT_VEC2,
			INT_VEC3			= gles::GL_INT_VEC3,
			INT_VEC4			= gles::GL_INT_VEC4,

			// Samplers //
			SAMPLER_2D			= gles::GL_SAMPLER_2D,
			SAMPLER_3D			= gles::OES_texture_3D::GL_SAMPLER_3D,
			SAMPLER_CUBE_MAP	= gles::GL_SAMPLER_CUBE,
			SAMPLER_2D_SHADOW	= gles::EXT_shadow_samplers::GL_SAMPLER_2D_SHADOW,
		};
	};



	//
	// Uniform
	//

	template <typename T>
	struct Uniform
	{
	// types
	public:
		typedef T			value_t;
		typedef Uniform<T>	Self;

		enum {
			TYPE_ID		= CachedUniform<T>::typeId,
			COUNT		= CachedUniform<T>::arraySize,
		};


	// variables
	private:
		gles::GLint			_id;
		T					_value;
		bool				_changed;

		DEBUG_ONLY(
			gles::GLuint		_progId;
			string				_name;
			gl_uniform::type	_type;
			uint				_size;	// array size
		);
		

	// methods
	public:
		Uniform ();
		Uniform (const ShaderProgram &prog, uni_c_string name);

		bool GetLocation (const ShaderProgram &prog, uni_c_string name);
		void SetValue (const T& value);

		bool IsValid () const	{ return _id != -1; }
	};



	//
	// Uniform Buffer (vec4 array)
	//

	template <typename T, usize SizeOf = sizeof(T)>
	struct UniformBuffer
	{
	// types
	private:
		enum {
			MAX_VERTEX_UNIFORM_VECTORS		= 128,									// GL_MAX_VERTEX_UNIFORM_VECTORS
			MAX_FRAGMENT_UNIFORM_VECTORS	= 16,									// GL_MAX_FRAGMENT_UNIFORM_VECTORS
			FLOAT_SIZE						= 4,
			VEC4_SIZE						= FLOAT_SIZE * 4,
			BUFFER_V4_SIZE					= (SizeOf + VEC4_SIZE-1) / VEC4_SIZE,	// number of uniform vec4 components
			BUFFER_SIZE						= BUFFER_V4_SIZE * VEC4_SIZE,			// size in bytes
		};

		typedef static_array< vec4, BUFFER_V4_SIZE >	uniform_buffer_t;
		typedef UniformBuffer< T, SizeOf >				Self;


	// variables
	private:
		uniform_buffer_t	_data;
		gles::GLuint		_id;
		bool				_changed;

		DEBUG_ONLY(
			gles::GLuint	_progId;
			string			_name;
			uint			_size;	// array size in bytes
		);


	// methods
	public:
		UniformBuffer ();
		UniformBuffer (const ShaderProgram &prog, uni_c_string name);

		bool SetData (const uni_array<const vec4> &data);
		bool GetLocation (const ShaderProgram &prog, uni_c_string name);

		void Update ();

		bool IsValid() const						{ return _id != -1; }

		uni_array<const vec4>	GetRawData() const	{ return _data; }
		uni_array<vec4>			GetRawData()		{ return _data; }

		T &						GetData()			{ return GetData<T>(); }
		T const &				GetData()	const	{ return GetData<T>(); }
		
		template <typename B>
		B &						GetData();
		
		template <typename B>
		B const &				GetData()	const;
	};

	

//----------------------------- Uniform ---------------------------//
	
	// constructor
	template <typename T>
	inline Uniform<T>::Uniform() : _id(0), _value(), _changed(true)
		DEBUG_ONLY( , _progId(0), _type(gl_uniform::_UNKNOWN), _size(0) )
	{}


	// constructor
	template <typename T>
	inline Uniform<T>::Uniform (const ShaderProgram &prog, uni_c_string name)
	{
		bool valid = GetLocation( prog, name );
		ASSERT( valid );
	}


	// GetLocation
	template <typename T>
	inline bool Uniform<T>::GetLocation (const ShaderProgram &prog, uni_c_string name)
	{
		using namespace gles;

		GL_CALL_R( _id = glGetUniformLocation( prog._id, name.cstr() ), false );
		CHECK_ERR( _id >= 0 );

		DEBUG_ONLY(
			_progId	= prog._id;
			prog._GetUniformInfo( _id, _name, (GLenum &)_type, _size );

			ASSERT( _name == name );
			ASSERT( _size == COUNT );
			ASSERT( _type == TYPE_ID );
		)
			
		return true;
	}
	

	// SetValue
	template <typename T>
	inline void Uniform<T>::SetValue (const T& value)
	{
		DEBUG_ONLY(
			ASSERT( ShaderProgram::_GetActiveProgram() == _progId );
		)

		CachedUniform<T>::Set( _id, _value, value );
	}



//-------------------------- UniformBuffer ------------------------//

	// constructor
	template <typename T, usize S>
	inline UniformBuffer<T,S>::UniformBuffer () :
		_data(), _id(-1), _changed(true)  DEBUG_ONLY(, _progId(0), _name(), _size(0) )
	{}
		

	// constructor
	template <typename T, usize S>
	inline UniformBuffer<T,S>::UniformBuffer (const ShaderProgram &prog, uni_c_string name) :
		_data(), _id(-1), _changed(true)  DEBUG_ONLY(, _progId(0), _name(), _size(0) )
	{
		GetLocation( prog, name );
		ASSERT( IsValid() );
	}
		

	// SetData
	template <typename T, usize S>
	inline bool UniformBuffer<T,S>::SetData (const uni_array<const vec4> &data)
	{
		DEBUG_ONLY( CHECK_ERR( data.Size() == _size ) );
		_data	 = data;
		_changed = true;
		return true;
	}


	// GetLocation
	template <typename T, usize S>
	inline bool UniformBuffer<T,S>::GetLocation (const ShaderProgram &prog, uni_c_string name)
	{
		using namespace gles;

		GL_CALL_R( _id = glGetUniformLocation( prog._id, name.cstr() ), false );
		CHECK_ERR( _id >= 0 );

		DEBUG_ONLY(
			GLenum	type = 0;

			_progId	= prog._id;
			prog._GetUniformInfo( _id, _name, type, _size );

			ASSERT( _name == name );

			switch ( type )
			{
				case gl_uniform::FLOAT :		_size *= 1;		break;
				case gl_uniform::FLOAT_VEC2 :	_size *= 2;		break;
				case gl_uniform::FLOAT_VEC3 :	_size *= 3;		break;
				case gl_uniform::FLOAT_VEC4 :	_size *= 4;		break;
				default :						WARNING( "invalid type for uniform buffer!" );
			}
			_size *= FLOAT_SIZE;
			ASSERT( _data.Size() <= _size );
		)
		return true;
	}
	

	// GetData
	template <typename T, usize S>
	template <typename B>
	inline B &  UniformBuffer<T,S>::GetData ()
	{
		STATIC_ASSERTE( sizeof(B) <= BUFFER_SIZE );

		_changed = true;
		return *(T *)_data.ptr();
	}
	

	// GetData
	template <typename T, usize S>
	template <typename B>
	inline B const &  UniformBuffer<T,S>::GetData () const
	{
		STATIC_ASSERTE( sizeof(B) <= BUFFER_SIZE );

		return *(const T *)_data.ptr();
	}
	

	// Update
	template <typename T, usize S>
	inline void UniformBuffer<T,S>::Update ()
	{
		using namespace gles;
		
		ASSERT( IsValid() );

		DEBUG_ONLY(
			ASSERT( ShaderProgram::_GetActiveProgram() == _progId );
		)

		_changed = false;
		GL_CALL( glUniform4fv( _id, _data.Count(), (const float *)_data.ptr() ) );
	}


}	// Graphics
}	// Engine